//
// Created by antler on 2019/1/8.
//

#include "ZipUtils.h"

#include "GlobalMacros.h"
#include "log.h"

#include "StringUtils.h"

#define UNZ_MAXFILENAMEINZIP 256

static const std::string emptyFilename("");

ZipFile *ZipFile::createWithBuffer(const void *buffer, unsigned long size) {
    ZipFile *zip = new(std::nothrow) ZipFile();
    if (zip && zip->initWithBuffer(buffer, size)) {
        return zip;
    }

    SAFE_DELETE(zip);
    return nullptr;
}

ZipFile::ZipFile()
        : _data(new ZipFilePrivate) {
    _data->zipFile = nullptr;
    _data->zipSubFile = nullptr;
}

ZipFile::~ZipFile() {
    if (_data) {
        if (_data->zipFile) {
            unzClose(_data->zipFile);
        }

        if (_data->zipSubFile) {
            unzClose(_data->zipSubFile);
        }
    }

    SAFE_DELETE(_data);
}

bool ZipFile::initWithBuffer(const void *buffer, unsigned long size) {
    if (!buffer || size == 0)
        return false;

    _data->zipFile = unzOpenBuffer(buffer, size);
    if (!_data->zipFile)
        return false;

    _data->fileList.clear();

    setFilter(_data->zipFile, false, emptyFilename);

    for (const auto &file : _data->fileList) {
        std::string filename = file.first;
        if (StringUtils::endWith(filename, XSC_FILE_EXT)) {
            ResizableBuffer buf;
            if (getFileData(filename, buf)) {
                if (!_data->zipSubFile) {
                    _data->zipSubFile = unzOpenBuffer(buf.data(), buf.size());
                    if (_data->zipSubFile) {
                        setFilter(_data->zipSubFile, true, emptyFilename);
                    }
                }
                break; // now only support one xsc
            }
        }
    }
    return true;
}

bool ZipFile::setFilter(unzFile zipFile, bool subFile, const std::string &filter) {
    bool ret = false;
    do {
        if (!_data || !zipFile) {
            break;
        }

        char szCurrentFileName[UNZ_MAXFILENAMEINZIP + 1];
        unz_file_info64 fileInfo;

        int err = unzGoToFirstFile64(zipFile, &fileInfo, szCurrentFileName,
                                     sizeof(szCurrentFileName) - 1);
        while (err == UNZ_OK) {
            unz64_file_pos posInfo;
            err = unzGetFilePos64(zipFile, &posInfo);
            if (err == UNZ_OK) {
                // ignore dir
                std::string currentFileName = szCurrentFileName;
                if (!StringUtils::endWith(currentFileName, "/")) {
                    if (filter.empty() || currentFileName.substr(0, filter.length()) == filter) {
                        LOGV("[setFilter] filename:%s, uncompressed_size:%lu",
                             currentFileName.c_str(),
                             (uLong) fileInfo.uncompressed_size);

                        ZipEntryInfo entry;
                        entry.pos = posInfo;
                        entry.uncompressed_size = fileInfo.uncompressed_size;
                        entry.subFile = subFile;
                        _data->fileList[currentFileName] = entry;
                    }
                }
            }

            err = unzGoToNextFile64(zipFile, &fileInfo, szCurrentFileName,
                                    sizeof(szCurrentFileName) - 1);
        }

        ret = true;

    } while (false);
    return ret;
}

bool ZipFile::fileExists(const std::string &fileName) const {
    bool ret = false;
    do {
        if (!_data) {
            break;
        }

        ret = _data->fileList.find(fileName) != _data->fileList.cend();
    } while (false);
    return ret;
}

bool ZipFile::getFileData(const std::string &fileName, ResizableBuffer &buffer) {
    bool ret = false;
    do {
        if (!_data) {
            break;
        }

        if (fileName.empty()) {
            break;
        }

        const auto iter = _data->fileList.find(fileName);
        if (iter == _data->fileList.cend()) {
            break;
        }

        ZipEntryInfo zipEntryInfo = iter->second;

        unzFile zipFile = zipEntryInfo.subFile ? _data->zipSubFile : _data->zipFile;
        if (!zipFile) {
            break;
        }

        int err = unzGoToFilePos64(zipFile, &zipEntryInfo.pos);
        if (err != UNZ_OK) {
            LOGV("[getFileData] unzGoToFilePos fileName:%s, err:%d", fileName.c_str(), err);
            break;
        }

        err = unzOpenCurrentFile(zipFile);
        if (err != UNZ_OK) {
            LOGV("[getFileData] unzOpenCurrentFile fileName:%s, err:%d", fileName.c_str(), err);
            break;
        }

        buffer.resize(static_cast<size_t>(zipEntryInfo.uncompressed_size));

        int nSize = unzReadCurrentFile(zipFile, (voidp) buffer.data(), buffer.size());
        if (nSize == 0 || nSize == (int) zipEntryInfo.uncompressed_size) {
            ret = true;
        }

        unzCloseCurrentFile(zipFile);

    } while (false);
    return ret;
}

bool ZipFile::getFileList(FileListContainer &fileList) const {
    do {
        if (!_data) {
            break;
        }

        fileList = _data->fileList;
        return true;
    } while (false);
    return false;
}